/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.cef.spi.entity.info.propertyinfo;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.inspur.edp.cef.api.manager.serialize.CefSerializeContext;
import com.inspur.edp.cef.api.manager.serialize.JsonFormatType;
import com.inspur.edp.cef.entity.changeset.ValueObjModifyChangeDetail;
import com.inspur.edp.cef.entity.entity.AssoInfoBase;
import com.inspur.edp.cef.spi.entity.AssociationEnableState;
import com.inspur.edp.cef.spi.entity.AssociationInfo;
import com.inspur.edp.cef.spi.jsonser.base.StringUtils;
import com.inspur.edp.cef.spi.jsonser.util.SerializerUtil;
import com.inspur.edp.udt.api.Manager.serialize.IAssoInfoSerializeItem;
import com.inspur.edp.udt.api.Manager.serialize.NestedSerializeContext;
import io.iec.edp.caf.common.JSONSerializer;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class AssocationPropertyInfo extends BasePropertyInfo {

    static final String EnrichedAssoInfoSerItemKey = "EnrichedPropInfos";
    private AssociationInfo associationInfo;
    private Class assoType;

    public AssocationPropertyInfo(AssociationInfo associationInfo) {
        this.associationInfo = associationInfo;
    }

    public AssocationPropertyInfo(AssociationInfo associationInfo,Class assoType)
    {
        this(associationInfo);
        this.assoType=assoType;
    }

    public AssocationPropertyInfo(String config,String nodeCode,String mainCode,
        AssociationEnableState enableState,String privateSourceColumn,String privateTargetColumn,Class assoType)
    {
        this(new AssociationInfo(config,nodeCode,mainCode,enableState,privateSourceColumn,privateTargetColumn),assoType);
    }

    public AssociationInfo getAssociationInfo() {
        return this.associationInfo;
    }

    public Class getAssoType() {
        return assoType;
    }

    public void setAssoType(Class assoType) {
        this.assoType = assoType;
    }

    @Override
    public void write(JsonGenerator writer, String propertyName, Object value, SerializerProvider serializer, CefSerializeContext serContext) {
        if (value == null) {
            SerializerUtil.writeBaseType(writer, value, propertyName, serializer);
            return;
        }

        //todo 后续考虑关联Udt是null的情况
        if(serContext.getJsonFormatType() == JsonFormatType.Tiled){
            writeTiled(writer, propertyName, value, serializer, serContext);
            return;
        }
        if(serContext.isKeepAssoPropertyForExpression()&&associationInfo.isKeepAssoPropertyForExpression())
        {
            writeAssoJsonValue(writer,propertyName+"___AssoExt", (AssoInfoBase) value,serializer,serContext);
            SerializerUtil.writeString(writer, value, propertyName, serializer);
        }
        else {
            writeAssoJsonValue(writer, propertyName, (AssoInfoBase) value, serializer, serContext);
        }
    }

    private void writeAssoJsonValue(JsonGenerator writer, String propertyName, AssoInfoBase value, SerializerProvider serializer, CefSerializeContext serContext) {
        AssoInfoBase asso = value;
        SerializerUtil.writeFieldName(writer, propertyName);
        SerializerUtil.writeStartObject(writer);
        String keyName = associationInfo.getPrivateTargetColumn();
        SerializerUtil.writeString(writer, asso.getValue(keyName), keyName, serializer);

        CefSerializeContext refSerContext = new CefSerializeContext() {{
            setEnableStdTimeFormat(serContext.getEnableStdTimeFormat());
        }};
        for (Map.Entry<String, DataTypePropertyInfo> refInfoItem : associationInfo.getRefPropInfos().entrySet()) {
//            if(refInfoItem.getValue().getObjectType() != ObjectType.Normal && refInfoItem.getValue().getObjectInfo() instanceof AssocationPropertyInfo){
//                refInfoItem.getValue().write(propertyName.substring(propertyName.lastIndexOf("_" + 1)), writer, asso.getValue(refInfoItem.getKey()), serializer, refSerContext);
//            }else {
            refInfoItem.getValue().write(writer, asso.getValue(refInfoItem.getKey()), serializer, refSerContext);
//            }
        }
        writeExtAssoInfo(writer, asso, serializer, serContext);

        SerializerUtil.writeEndObject(writer);
    }

    public void writeTiled(JsonGenerator writer, String propertyName, Object value, SerializerProvider serializer, CefSerializeContext serContext) {
        if (value == null) {
            SerializerUtil.writeBaseType(writer, value, propertyName, serializer);
            return;
        }

        AssoInfoBase asso = (AssoInfoBase) value;
        String keyName = associationInfo.getPrivateTargetColumn();
        if(propertyName.equalsIgnoreCase(keyName)){
            SerializerUtil.writeString(writer, asso.getValue(keyName), keyName, serializer);
        }
        else {
            SerializerUtil.writeString(writer, asso.getValue(keyName), propertyName.substring(0, propertyName.lastIndexOf("_")) + "_" + keyName, serializer);
        }

        CefSerializeContext refSerContext = new CefSerializeContext() {{
            setEnableStdTimeFormat(serContext.getEnableStdTimeFormat());
            setJsonFormatType(serContext.getJsonFormatType());
        }};
        for (Map.Entry<String, DataTypePropertyInfo> refInfoItem : associationInfo.getRefPropInfos().entrySet()) {
            if(propertyName.equalsIgnoreCase(keyName)){
                refInfoItem.getValue().write("", writer, asso.getValue(refInfoItem.getKey()), serializer, refSerContext);
            }
            else {
                refInfoItem.getValue().write(propertyName.substring(0, propertyName.lastIndexOf("_")), writer, asso.getValue(refInfoItem.getKey()), serializer, refSerContext);
            }
        }
        writeExtAssoInfo(writer, asso, serializer, serContext);
    }

    @Override
    public void writeChange(JsonGenerator writer, String propertyName, Object value, SerializerProvider serializer, CefSerializeContext serContext) {
        write(writer, propertyName, value, serializer, serContext);
    }

    protected AssoInfoBase createNewInstance() {
        try {
            return (AssoInfoBase) assoType.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("类型" + assoType.getName() + "实例化失败", e);
        }
    }

    @Override
    public Object read(JsonParser reader, String propertyName, DeserializationContext serializer, CefSerializeContext serContext) {
        return readAssociation(reader, serializer, propertyName, serContext);
//        HashMap<String,Object> valueDic = readAssociationValue(reader,serializer);
//        for (String dicKey : valueDic.keySet()) {
//            Object propertyValue = valueDic.get(dicKey);
//            if (dicKey.equals(propertyName) || dicKey.equals(StringUtils.toCamelCase(propertyName))){
//                asso.setValue(propertyName, SerializerUtil.getString(propertyValue));
//                continue;
//            }
//
//            if(associationInfo.getRefPropInfos().containsKey(dicKey)){
//                DataTypePropertyInfo propInfo = associationInfo.getRefPropInfos().get(dicKey);
//                Object refValue = propInfo.read(reader, serializer);
//                asso.setValue();
//            }
//
//        }

    }

    @Override
    public Object read(JsonNode node, String propertyName, CefSerializeContext serContext) {
        return null;
    }

    @Override
    public Object readChange(JsonParser reader, String propertyName, DeserializationContext serializer, CefSerializeContext serContext) {
        return read(reader, propertyName, serializer, serContext);
    }

    @Override
    public Object readChange(JsonNode node, String propertyName, CefSerializeContext serContext) {
        AssoInfoBase asso = createNewInstance();
        asso.setValue(propertyName, node.textValue());
        return asso;
    }

    private AssoInfoBase readAssociation(JsonParser p, DeserializationContext ctxt, String propertyName, CefSerializeContext serContext) {
        if (p.getCurrentToken() == JsonToken.VALUE_NULL) {
            return null;
        }
        if (serContext.getJsonFormatType() == JsonFormatType.Tiled) {
            return readTiledAssociation(p, ctxt, propertyName, serContext);
        }
        if (p.getCurrentToken() != JsonToken.START_OBJECT) {
            throw new RuntimeException("json结构中关联字段[" + propertyName + "]的值应为对象类型, 当前类型为[" + p.getCurrentToken()+"], 请检查前端网络请求内容");
        }
        AssoInfoBase asso = createNewInstance();
        try {
            p.nextToken();
            while (p.hasCurrentToken()) {
                if (p.getCurrentToken() == JsonToken.END_OBJECT) {
                    break;
                }
                if (p.getCurrentToken() != JsonToken.FIELD_NAME) {
                    p.nextToken();
                    continue;
                }

                String refPropertyName = p.getValueAsString();
                p.nextToken();
                readRefValue(p, ctxt, asso, propertyName, refPropertyName, serContext);
                p.nextToken();
            }
        } catch (IOException e) {
            throw new RuntimeException("关联带出结构反序列化失败:" + p.toString(), e);
        }

        return asso;
    }

    private AssoInfoBase readTiledAssociation(JsonParser p, DeserializationContext ctxt, String propertyName, CefSerializeContext serContext) {

        AssoInfoBase asso = createNewInstance();
        asso.setValue(propertyName, SerializerUtil.readString(p));
        return asso;

    }

    private void readRefValue(JsonParser p, DeserializationContext ctxt, AssoInfoBase asso, String propertyName, String refPropertyName, CefSerializeContext serContext) {
        if (refPropertyName.equals(propertyName) || refPropertyName.equals(StringUtils.toCamelCase(propertyName))) {
            asso.setValue(propertyName, SerializerUtil.readString(p));
            return;
        }
        String keyName = associationInfo.getPrivateTargetColumn();
        if (refPropertyName.equals(keyName) || refPropertyName.equals(StringUtils.toCamelCase(keyName))) {
            asso.setValue(keyName, SerializerUtil.readString(p));
            return;
        }
        if (associationInfo.getRefPropInfos().containsKey(refPropertyName)) {
            DataTypePropertyInfo propInfo = associationInfo.getRefPropInfos().get(refPropertyName);
            Object refValue = null;
            if (p.getCurrentToken() != JsonToken.VALUE_NULL) {
                refValue = propInfo.read(p, ctxt, serContext);
            }
            asso.setValue(propInfo.getPropertyName(), refValue);
            return;
        }
        //TODO: 不抛异常??
        try {
            p.readValueAsTree();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private HashMap<String, Object> readAssociationValue(JsonParser p, DeserializationContext ctxt) {
        java.util.HashMap<String, Object> transValue = new java.util.HashMap<String, Object>();
        if (p.getCurrentToken() == JsonToken.VALUE_NULL) {
            return transValue;
        }

        ObjectMapper mapper = JSONSerializer.getObjectMapper();
        JsonNode value = null;
        try {
            value = mapper.readValue(p, JsonNode.class);
        } catch (IOException e) {
            throw new RuntimeException("关联Json获取失败");
        }

        if (value != null) {
            Iterator<Map.Entry<String, JsonNode>> fields = value.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> item = fields.next();
                String propName = item.getKey();
                JsonNode changeValue = item.getValue();

                if (changeValue.isObject()) {
                    transValue.put(propName, changeValue);
                } else {
                    try {
                        Object objValue = mapper.treeToValue(changeValue, Object.class);
                        if (objValue != null) {
                            transValue.put(propName, objValue);
                        }
                    } catch (JsonProcessingException e) {
                        throw new RuntimeException(changeValue.toString() + "转换失败");
                    }
                }
            }
        }
        return transValue;
    }

    protected void writeExtAssoInfo(JsonGenerator writer, Object info,
                                    SerializerProvider serializerProvider, CefSerializeContext serContext) {
        IAssoInfoSerializeItem assoSerItem = getAssoInfoSerItem(serContext);
        if (assoSerItem == null) {
            return;
        }

        assoSerItem.writeAssociationInfo(writer, info, serializerProvider);
    }

    private IAssoInfoSerializeItem getAssoInfoSerItem(CefSerializeContext ctx) {
        if (!(ctx instanceof NestedSerializeContext)) {
            return null;
        }
        return ((NestedSerializeContext) ctx).getAssociationEnrichedSerializer();
    }

    public Object createAssociationDefaultValue() {
        try {
            return assoType.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException();
        } catch (IllegalAccessException e) {
            throw new RuntimeException();
        }
    }

    @Override
    public void setValue(Object data, String propertyName, Object value, CefSerializeContext serContext){
        AssoInfoBase asso = (AssoInfoBase)data;
        if(asso == null)
            return;
        JsonNode node = (JsonNode)value ;
        String keyName = associationInfo.getPrivateTargetColumn();
        if (propertyName.equals(keyName) || propertyName.equals(StringUtils.toCamelCase(keyName))) {
            asso.setValue(keyName, node.textValue());
            return;
        }
        if (!associationInfo.getRefPropInfos().containsKey(propertyName))
            return;
        DataTypePropertyInfo refPropertyInfo = associationInfo.getRefPropInfos().get(propertyName);
        Object refValue = refPropertyInfo.read(node, serContext);
        asso.setValue(refPropertyInfo.getPropertyName(), refValue);
    }

    @Override
    public Object createValue() {
        return createNewInstance();
    }

    @Override
    public Object createChange(){
        return createValue();
    }

    public final void addRefProperty(String propertyName,String refPropertyName)
    {
        addRefProperty(propertyName,"",refPropertyName);
    }

    public final void addRefProperty(String propertyName,String propertyDisplayKey,String refPropertyName)
    {
        getAssociationInfo().addRefProperty(propertyName, propertyDisplayKey,refPropertyName);
    }

    public final void addEnrichedRefProperty(String propertyName,String refPropertyName)
    {
        addEnrichedRefProperty(propertyName,"",refPropertyName);
    }
    public final void addEnrichedRefProperty(String propertyName,String propertyDisplayKey,String refPropertyName)
    {
        getAssociationInfo().addEnrichedRefProperty(propertyName, propertyDisplayKey,refPropertyName);
    }


    private  HashMap<String,String> assDbMapping;

  public HashMap<String, String> getAssDbMapping() {
    return assDbMapping;
  }

  public void setAssDbMapping(HashMap<String, String> assDbMapping) {
    this.assDbMapping = assDbMapping;
  }

  public String getPropValue(Object value){
      if (value == null) {
          return "";
      }
      AssoInfoBase asso = (AssoInfoBase)value;
      StringBuilder returnString = new StringBuilder();
      String keyName = associationInfo.getPrivateTargetColumn();
      returnString.append("[{\""+keyName+"\":\""+asso.getValue(keyName)+"\"");
      for (Map.Entry<String, DataTypePropertyInfo> refInfoItem : associationInfo.getRefPropInfos().entrySet()) {
//            if(refInfoItem.getValue().getObjectType() != ObjectType.Normal && refInfoItem.getValue().getObjectInfo() instanceof AssocationPropertyInfo){
//                refInfoItem.getValue().write(propertyName.substring(propertyName.lastIndexOf("_" + 1)), writer, asso.getValue(refInfoItem.getKey()), serializer, refSerContext);
//            }else {
          String propname = refInfoItem.getKey();
          Object propvalue = asso.getValue(propname);
          returnString.append(",\""+propname+"\":"+refInfoItem.getValue().getPropValue(propvalue));
//            }
      }
      returnString.append("}]");
      return returnString.toString();
  }
}
